#include "preHeader.h"
#include "Creature.h"
#include "Loot/CreatureLooterMgr.h"
#include "Map/MapInstance.h"
#include "Map/VacuousObject.inl"
#include "Session/GameServerMgr.h"

Creature::Creature(OBJECT_TYPE objType)
: Unit(objType)
, m_pSpawn(NULL)
, m_pLooterMgr(NULL)
, m_pNextWP(NULL)
, m_iPatrolMoveCD(0)
, m_isBehaviorTree(false)
{
}

Creature::~Creature()
{
}

void Creature::SubInitLuaEnv()
{
	m_blackboard.SetActor(m_pMapInstance->L, this);
	this->binder::setfinal();
}

bool Creature::InitCreature(const CreatureSpawn* pSpawn)
{
	m_pSpawn = pSpawn;
	m_spawnPos = {pSpawn->x, pSpawn->y, pSpawn->z};
	SetPosition({pSpawn->x, pSpawn->y, pSpawn->z});
	SetOrientation(pSpawn->o);

	if (!Init(pSpawn->entry)) {
		return false;
	}

	return true;
}

bool Creature::SubInit()
{
	AoiActor::SetRadius(m_pProto->senseRadius);
	SetS32Value(UNIT_FIELD_LEVEL,
		m_pSpawn->level != 0 ? m_pSpawn->level : m_pProto->level);
	m_hpMaxReachable = m_mpMaxReachable = INT64_MAX;
	ResetIdlePatrol();
	return true;
}

void Creature::SubUpdate(uint64 diffTime)
{
	UpdateTarget();
	if (m_isBehaviorTree) {
		RunBehaviorTree();
	}
}

void Creature::OnPushToWorld()
{
	InitLuaEnv();
	if (m_pProto->aiScriptId != 0) {
		auto& scriptFile = GetScriptFileById(m_pProto->aiScriptId);
		if (!scriptFile.empty()) {
			BuildBehaviorTree(m_pMapInstance->L, scriptFile.c_str());
			m_isBehaviorTree = true;
		}
	}
	if (m_pProto->spawnScriptId != 0) {
		RunScriptFileById(m_pMapInstance->L, m_pProto->spawnScriptId,
			this, std::string_view(m_pProto->spawnScriptArgs));
	}
	if (m_pProto->playScriptId != 0) {
		RunScriptFileById(m_pMapInstance->L, m_pProto->playScriptId,
			this, std::string_view(m_pProto->playScriptArgs));
	}

	if (m_pProto->charFlags.isActivity) {
		NetPacket pack(MS_UPDATE_ACTIVITY_OBJECT_POSITION);
		pack << true << m_guid << m_pProto->charTypeId;
		pack << GetInstGuid() << GetPosition();
		sGameServerMgr.BroadcastPacket2AllGameServer(pack);
	}
	Unit::OnPushToWorld();
}

void Creature::OnPreLeaveWorld()
{
	RemoveAllEtherealObjects();
	Unit::OnPreLeaveWorld();
}

void Creature::OnDelete()
{
	if (m_pProto->charFlags.isActivity) {
		NetPacket pack(MS_UPDATE_ACTIVITY_OBJECT_POSITION);
		pack << false << m_guid << m_pProto->charTypeId;
		pack << GetInstGuid();
		sGameServerMgr.BroadcastPacket2AllGameServer(pack);
	}

	if (m_pSpawn->flags.isRespawn) {
		m_pMapInstance->CreateTimerX([pMapInstance = m_pMapInstance,
			pSpawnPrivate = m_pSpawnPrivate, pSpawn = m_pSpawn]() {
			auto pCreature = pMapInstance->SpawnCreature(pSpawn);
			if (pCreature != NULL && pSpawnPrivate) {
				pCreature->SetSpawnPrivate(pSpawnPrivate);
			}
		}, System::Randi(m_pProto->minRespawnTime, m_pProto->maxRespawnTime), 1);
	}
	Unit::OnDelete();
}

void Creature::BuildCreatePacketForPlayer(INetPacket& pck, Player* pPlayer)
{
	Unit::BuildCreatePacketForPlayer(pck, pPlayer);
	pck << m_pSpawn->spawnId;
}

void Creature::SetSpawnPrivate(const std::shared_ptr<CreatureSpawn>& pSpawn)
{
	m_pSpawnPrivate = pSpawn;
}

const std::shared_ptr<CreatureSpawn>& Creature::GetSpawnPrivate() const
{
	return m_pSpawnPrivate;
}

void Creature::SetTarget(Unit* pTarget)
{
	Unit::SetTarget(pTarget);
	if (pTarget != NULL) {
		SetInCombat(true);
	}
}

Unit* Creature::SelectMaxHatredEnemy()
{
	Unit* pSltEnemy = NULL;
	uint32 sltHatred = 0;
	float sltDistSq = .0f;
	auto itr = m_enemyList.begin();
	while (itr != m_enemyList.end()) {
		const EnemyInfo& enemyInfo = itr++->second;
		auto pEnemy = m_pMapInstance->GetUnit(enemyInfo.enemy);
		if (!IsEnemyAvail(pEnemy, true)) {
			itr = m_enemyList.erase(itr);
			continue;
		}
		auto hatred = enemyInfo.hatred;
		auto distSq = GetDistanceSq(pEnemy);
		if (pSltEnemy == NULL) {
mark:		pSltEnemy = pEnemy;
			sltHatred = hatred, sltDistSq = distSq;
			continue;
		}
		if (sltHatred > hatred) {
			continue;
		} else if (sltHatred < hatred) {
			goto mark;
		}
		if (sltDistSq < distSq) {
			continue;
		} else if (sltDistSq > distSq) {
			goto mark;
		}
		continue;
	}
	return pSltEnemy;
}

void Creature::UpdateTarget()
{
	CleanEnemyList();
	CleanEnemySenseList();
	auto pEnemy = SelectMaxHatredEnemy();
	if (pEnemy != NULL && pEnemy != m_pTarget) {
		SetTarget(pEnemy);
	}
}

void Creature::CleanEnemyList()
{
	if (m_pTarget != NULL) {
		if (!IsEnemyAvail(m_pTarget, true)) {
			m_enemyList.erase(m_pTarget->GetGuid());
			SetTarget(NULL);
		}
	}

	auto itr = m_enemyList.begin();
	while (itr != m_enemyList.end()) {
		auto pEnemy = m_pMapInstance->GetUnit(itr->first);
		if (!IsEnemyAvail(pEnemy, false)) {
			itr = m_enemyList.erase(itr);
		} else {
			++itr;
		}
	}
}

bool Creature::IsEnemyAvail(Unit* pEnemy, bool isCheckAttack)
{
	if (pEnemy == NULL || pEnemy->IsDead()) {
		return false;
	}
	if (pEnemy->GetDistanceSq(m_spawnPos) > SQ(m_pProto->combatRadius)) {
		return false;
	}
	if (isCheckAttack && !CanAttack(pEnemy)) {
		return false;
	}
	return true;
}

bool Creature::IsHostile(const Unit* pTarget) const
{
	if (pTarget == this || pTarget == NULL) {
		return false;
	}
	if (!m_pProto->charFlags.isJoinCombat) {
		return false;
	}

	// case player
	if (pTarget->IsKindOf(TYPE_PLAYER)) {
		auto pTargetPlayer = static_cast<const Player*>(pTarget);
		if (m_pProto->charRace == (u32)CharRaceType::HostileForces) {
			return true;
		}
		if (m_pProto->charRace == (u32)CharRaceType::FriendlyForces) {
			return false;
		}
		if (GetTeamSide() != ArenaTeamSide::teamInvalid &&
			pTargetPlayer->GetTeamSide() != ArenaTeamSide::teamInvalid) {
			return GetTeamSide() != pTargetPlayer->GetTeamSide() &&
				m_pMapInstance->IsFightingStage();
		}
		return false;
	}
	// case creature
	else if (pTarget->IsType(TYPE_CREATURE)) {
		auto pTargetCreature = static_cast<const Creature*>(pTarget);
		auto pTargetProto = pTargetCreature->GetProto();
		if (!pTargetProto->charFlags.isJoinCombat) {
			return false;
		}
		if (pTargetProto->charRace == (u32)CharRaceType::HostileForces &&
			m_pProto->charRace == (u32)CharRaceType::FriendlyForces) {
			return true;
		}
		if (pTargetProto->charRace == (u32)CharRaceType::FriendlyForces &&
			m_pProto->charRace == (u32)CharRaceType::HostileForces) {
			return true;
		}
		if (GetTeamSide() != ArenaTeamSide::teamInvalid &&
			pTargetCreature->GetTeamSide() != ArenaTeamSide::teamInvalid) {
			return GetTeamSide() != pTargetCreature->GetTeamSide() &&
				m_pMapInstance->IsFightingStage();
		}
		return false;
	}

	return false;
}

void Creature::OnLoseHP(Unit* pHurter, uint64 hurtValue)
{
	if (pHurter != NULL && pHurter->IsType(TYPE_PLAYER)) {
		AddToLooterList((Player*)pHurter, hurtValue);
	}
	Unit::OnLoseHP(pHurter, hurtValue);
}

void Creature::OnDead(Unit* pKiller)
{
	if (m_pProto->deadScriptId != 0) {
		RunScriptFileById(m_pMapInstance->L, m_pProto->deadScriptId,
			this, std::string_view(m_pProto->deadScriptArgs));
	}
	if (pKiller->IsType(TYPE_PLAYER)) {
		((Player*)pKiller)->GetQuestStorage()->OnKillCreature(this);
	}
	Loot();
	CreateTimerX([=]() {
		FastDisappear();
	}, m_pProto->deadDisappearTime, 1);
	Unit::OnDead(pKiller);
}

void Creature::OnResetToIdle()
{
	MoveToPosition(GetSpawnPos());
	SetMoveMode(MoveByRun);
	SetPriorMove(NULL);

	RecoveryHPMPValue4ResetToIdle();
	ResetIdlePatrol();
	ClearLooterList();

	Unit::OnResetToIdle();
}

void Creature::RecoveryHPMPValue4ResetToIdle()
{
	auto hpRate = m_attribute.GetAttrEx(ATTREXTYPE::RECOVERY_HP_RATE);
	auto hpValue = m_attribute.GetAttrEx(ATTREXTYPE::RECOVERY_HP_VALUE);
	if (hpRate < 0 && hpValue < 0) {
		AddHPValue(INT_MAX);
	}

	AddMPValue(INT_MAX);
}

void Creature::AddToLooterList(Player* pPlayer, uint64 damage)
{
	if (m_pProto->lootSetID == 0) {
		return;
	}
	if (unlikely(m_pLooterMgr == NULL)) {
		m_pLooterMgr = new CreatureLooterMgr(this);
	}
	m_pLooterMgr->AddScore(pPlayer, damage);
}

void Creature::ClearLooterList()
{
	SAFE_DELETE(m_pLooterMgr);
}

void Creature::Loot()
{
	if (m_pLooterMgr != NULL) {
		auto pLooter = m_pLooterMgr->CalcFinalLooter();
		if (pLooter != NULL) {
			pLooter->LootPrizes2ItemStorage(
				m_pProto->lootSetID, IFT_LOOT, {GetEntry()},
				CFT_LOOT, {GetEntry()}, true);
		}
	}
}

bool Creature::CanIdlePatrol() const
{
	return m_pSpawn->idleType != CreatureIdleNone;
}

void Creature::UpdateIdlePatrol()
{
	if (m_iPatrolMoveCD >= GET_SYS_TIME) {
		return;
	}
	if (m_pNextWP == NULL) {
		UpdateRandomPoint();
	} else {
		UpdateWayPoint();
	}
	switch (m_pSpawn->idleType) {
	case CreatureIdleWalkPatrol: SetMoveMode(MoveByWalk); break;
	case CreatureIdleRunPatrol: SetMoveMode(MoveByRun); break;
	}
}

void Creature::UpdateRandomPoint()
{
	vector3f tgtPos;
	int rst = m_pMapInstance->getGameMap().GetScene()->Raycast(m_position,
		RandomPositionByRange(m_spawnPos, m_pProto->patrolRange), tgtPos,
		m_pMapInstance->GetNavPhysicsFlags());
	if (rst >= 0) {
		MoveToPosition(tgtPos);
	}
	m_iPatrolMoveCD = GET_SYS_TIME + System::Rand(0, CREATURE_PATROL_MOVE_CD);
}

void Creature::UpdateWayPoint()
{
	MoveToPosition({m_pNextWP->x, m_pNextWP->y, m_pNextWP->z});
	if (m_pNextWP->keep_idle == -1) {
		m_iPatrolMoveCD = 0;
	} else if (m_pNextWP->keep_idle == 0) {
		m_iPatrolMoveCD = GET_SYS_TIME + System::Rand(0, CREATURE_PATROL_MOVE_CD);
	} else if (m_pNextWP->keep_idle > 0) {
		m_iPatrolMoveCD = GET_SYS_TIME + m_pNextWP->keep_idle;
	} else {
		m_iPatrolMoveCD = GET_SYS_TIME + System::Rand(0, -m_pNextWP->keep_idle);
	}
	m_pNextWP = m_pNextWP->next_wp_id != 0 ?
		GetDBEntry<WayPoint>(m_pNextWP->next_wp_id) : NULL;
}

void Creature::ResetIdlePatrol()
{
	m_pNextWP = m_pSpawn->wayPointId != 0 ?
		GetDBEntry<WayPoint>(m_pSpawn->wayPointId) : NULL;
	m_iPatrolMoveCD = GET_SYS_TIME + System::Rand(0, CREATURE_PATROL_MOVE_CD);
}

bool Creature::IsVisibleByPlayer(const Player* pPlayer)
{
	if (pPlayer->IsInvisibleCreature(GetSpawnId())) {
		return false;
	}
	return true;
}

void Creature::OnChangePosition()
{
	MoveEtherealObjects();
	Unit::OnChangePosition();
}

Creature::EtherealObject* Creature::AttachEtherealObject(float radius, int flags)
{
	EtherealObject* pEObj = new EtherealObject(this, radius, flags);
	m_pMapInstance->PushAoiActorOrder(pEObj, m_position.x, m_position.z);
	m_etherealObjects.push_back(pEObj);
	return pEObj;
}

void Creature::DetachEtherealObject(EtherealObject* pEObj)
{
	auto itr = std::find(m_etherealObjects.begin(), m_etherealObjects.end(), pEObj);
	if (itr != m_etherealObjects.end()) {
		m_etherealObjects.erase(itr);
		m_pMapInstance->PopupAoiActorOrder(pEObj, true);
	}
}

void Creature::RemoveAllEtherealObjects()
{
	while (!m_etherealObjects.empty()) {
		auto pEObj = m_etherealObjects.back();
		m_etherealObjects.pop_back();
		m_pMapInstance->PopupAoiActorOrder(pEObj, true);
	}
}

void Creature::MoveEtherealObjects()
{
	for (auto pEObj : m_etherealObjects) {
		m_pMapInstance->MoveAoiActorOrder(pEObj, m_position.x, m_position.z);
	}
}

void Creature::ObjectHookEvent_OnEObjUnitEnter(EtherealObject* pEObj, Unit* pUnit)
{
	ForeachHookInfo4Event(m_objectHookInfos, ObjectHookEvent::OnEObjUnitEnter, "OnEObjUnitEnter", pEObj, pUnit);
}

void Creature::ObjectHookEvent_OnEObjUnitLeave(EtherealObject* pEObj, Unit* pUnit)
{
	ForeachHookInfo4Event(m_objectHookInfos, ObjectHookEvent::OnEObjUnitLeave, "OnEObjUnitLeave", pEObj, pUnit);
}
